package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"prod/api/prod"
	"prod/internal/constant"
	"prod/internal/model"
	"prod/pkg/ecode"
	"prod/pkg/orm"
	"prod/pkg/util"
	"strconv"
	"strings"
	"time"
)

func (s *Service) GetProdNatureStats(ctx context.Context, req *prod.CommonRequest) (
	resp *prod.GetProdNatureResponse, err error) {
	if err = s.dao.CheckPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	resp = &prod.GetProdNatureResponse{}
	if resp.NatureList, err = s.dao.LoadProdStatsByNature(ctx, req.Token.SgId, req.PStoreId); err != nil {
		return
	}
	return
}

func (s *Service) getPriceRangeDict(skus []*model.Sku) (priceRangeDict map[uint64][]uint32) {
	priceRangeDict = make(map[uint64][]uint32)
	for _, sku := range skus {
		if skuPrice, ok := priceRangeDict[sku.ProdId]; !ok {
			priceRangeDict[sku.ProdId] = []uint32{sku.Price, sku.Price}
		} else if skuPrice[0] > sku.Price {
			priceRangeDict[sku.ProdId][0] = sku.Price
		} else if skuPrice[1] < sku.Price {
			priceRangeDict[sku.ProdId][1] = sku.Price
		}
	}
	return
}

func (s *Service) getSkuMapByIds(skus []*model.Sku) (skuMap map[uint64][]*prod.SimpleSkuView) {
	skuMap = make(map[uint64][]*prod.SimpleSkuView)
	for _, sku := range skus {
		if _, ok := skuMap[sku.ProdId]; !ok {
			skuMap[sku.ProdId] = make([]*prod.SimpleSkuView, 0)
		}
		skuMap[sku.ProdId] = append(skuMap[sku.ProdId], &prod.SimpleSkuView{
			Id:    sku.ID,
			Name:  strings.Join(sku.Items, "/"),
			Price: sku.Price,
		})
	}
	return
}

func (s *Service) GetProdList(ctx context.Context, req *prod.GetProdListRequest) (
	resp *prod.GetProdListResponse, err error) {
	if err = s.dao.CheckPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	resp = &prod.GetProdListResponse{}
	if resp.Prods, resp.Total, err = s.dao.LoadProdListByPage(
		ctx, req.Token.SgId, req.PStoreId, req.ProdTypeId, req.PageNo, req.PageSize,
		req.Nature, req.Query, req.ExcludedProdIds, req.ExcludedMenuId); err != nil {
		return
	}
	if len(resp.Prods) != 0 {
		prodIds := make([]uint64, len(resp.Prods))
		typeMap := make(util.Int32BoolMap)
		for i, product := range resp.Prods {
			prodIds[i] = product.Id
			typeMap[product.ProdTypeId] = true
		}
		var (
			prodTypeMap map[uint32]string
			skus        []*model.Sku
		)
		if prodTypeMap, err = s.getProdTypeMapByIds(
			ctx, typeMap.GetKeys(), req.Token.SgId, req.PStoreId); err != nil {
			return
		}
		if skus, err = s.dao.GetProdSkusByProdIds(ctx, prodIds, req.Token.SgId); err != nil {
			return
		}
		priceRangeDict := s.getPriceRangeDict(skus)
		skuMap := s.getSkuMapByIds(skus)
		for _, product := range resp.Prods {
			product.ProdTypeName = prodTypeMap[product.ProdTypeId]
			product.PriceRange = priceRangeDict[product.Id]
			product.Skus = skuMap[product.Id]
		}
	}
	return
}

func (s *Service) getProdInfo(ctx context.Context, prodId uint64, sgId, pStoreId uint32, skipCheck bool) (
	resp *prod.ProdView, err error) {
	var (
		product   *model.Product
		skus      []*model.Sku
		prodTypes []*prod.SimpleProdTypeView
	)
	if !skipCheck {
		if product, err = s.dao.GetProdInfo(ctx, prodId, sgId, pStoreId); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
	} else {
		if product, err = s.dao.GetProdInfoWithStatus(ctx, prodId, sgId); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
	}

	if skus, err = s.dao.GetProdSkusByProdIds(ctx, []uint64{prodId}, sgId); err != nil {
		return
	}
	if prodTypes, err = s.dao.GetSimpleProdTypeByIDs(
		ctx, []uint32{product.TypeId}, sgId, product.PStoreId); err != nil {
		return
	}
	resp = &prod.ProdView{
		Id:                product.ID,
		Name:              product.Name,
		ProdTypeId:        product.TypeId,
		ProdTypeName:      prodTypes[0].Name,
		Phonetic:          product.Phonetic,
		Unit:              product.Unit,
		TitlePics:         product.TitlePics,
		DetailPics:        product.DetailPics,
		DetailDescription: product.Description,
		Groups:            make([]*prod.ProdPropertyGroupView, len(product.GroupList)),
		Skus:              make([]*prod.SkuView, len(skus)),
		Nature:            prod.Nature(product.Nature),
		Status:            product.Status.Bool,
	}
	if len(product.GroupList) != 0 {
		groupDetailMap := make(map[string][]string)
		if err = json.Unmarshal(product.GroupDetail, &groupDetailMap); err != nil {
			err = ecode.WithStack(err)
			return
		}
		for i, groupName := range product.GroupList {
			resp.Groups[i] = &prod.ProdPropertyGroupView{
				Name:  groupName,
				Items: groupDetailMap[groupName],
			}
		}
	}
	for i, sku := range skus {
		var skuName string
		if len(sku.Items) == 0 {
			skuName = product.Name
		} else {
			skuName = strings.Join(sku.Items, "/")
		}
		resp.Skus[i] = &prod.SkuView{
			Id:     sku.ID,
			Name:   skuName,
			Price:  sku.Price,
			Weight: sku.Weight,
			Items:  sku.Items,
		}
	}
	return
}

func (s *Service) GetProdInfo(ctx context.Context, req *prod.GetProdInfoRequest) (
	resp *prod.ProdView, err error) {
	if err = s.dao.CheckPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	resp, err = s.getProdInfo(ctx, req.Id, req.Token.SgId, req.PStoreId, false)
	return
}

func (s *Service) verifyProdType(ctx context.Context, prodTypeId, sgId, pStoreId uint32, nature prod.Nature) (
	prodType *model.ProdType, err error) {
	var parentType *model.ProdType
	if parentType, err = s.dao.GetProdTypeByID(ctx, prodTypeId, sgId, pStoreId); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = constant.ErrObjectNotExist
		}
		return
	} else if parentType.ParentId == 0 {
		err = ecode.AppendMessage(constant.ErrForbiddenAction, "商品只能建立在二级类目下")
		return
	} else if nature != prod.Nature(parentType.Nature) {
		err = ecode.BadRequest("商品类型与类目类型不匹配")
		return
	}
	return
}

func (s *Service) verifySkuList(rawSkuList []*prod.CreateSkuView, sgId, operator uint32) (
	skus []*model.Sku, groupList []string, groupDetail orm.JSON, err error) {
	groupList = make([]string, len(rawSkuList[0].Items))
	groupMap := make(util.StringBoolMap)
	groupItemMap := make(map[string]util.StringBoolMap)
	skuNameMap := make(util.StringBoolMap)
	groupDetailMap := make(map[string][]string)
	for i, itemView := range rawSkuList[0].Items {
		groupList[i] = itemView.Name
		groupMap[itemView.Name] = true
		groupItemMap[itemView.Name] = make(util.StringBoolMap)
		groupDetailMap[itemView.Name] = make([]string, 0)
	}
	if len(groupMap) != len(groupList) {
		err = ecode.BadRequest("输入存在重名分组")
		return
	}
	for _, sku := range rawSkuList {
		var skuItems []string
		for _, item := range sku.Items {
			if _, ok := groupItemMap[item.Name]; !ok {
				err = ecode.BadRequest("规格之间分组未对齐")
				return
			}
			if _, ok := groupItemMap[item.Name][item.Item]; !ok {
				groupItemMap[item.Name][item.Item] = true
				groupDetailMap[item.Name] = append(groupDetailMap[item.Name], item.Item)
			}
			skuItems = append(skuItems, item.Item)
		}
		skuName := strings.Join(skuItems, "/")
		if _, ok := skuNameMap[skuName]; ok {
			err = ecode.BadRequest("存在同名规格")
			return
		}
		skuNameMap[skuName] = true
		skus = append(skus, &model.Sku{
			Items:    skuItems,
			Price:    sku.Price,
			Weight:   sku.Weight,
			SgId:     sgId,
			Operator: operator,
			Status:   true,
		})
	}
	if groupDetail, err = json.Marshal(groupDetailMap); err != nil {
		err = ecode.WithStack(err)
		return
	}
	return
}

func (s *Service) CreateProd(ctx context.Context, req *prod.CreateProdRequest) (resp *prod.ProdView, err error) {
	if err = s.dao.CheckPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	var (
		existProd *model.Product
		skus      []*model.Sku
	)
	if _, err = s.verifyProdType(ctx, req.ProdTypeId, req.Token.SgId, req.PStoreId,
		req.Nature); err != nil {
		return
	}
	if existProd, err = s.dao.GetProdInfoByName(ctx, req.Name, req.Token.SgId, req.PStoreId); err != nil {
		if errors.Cause(err) != gorm.ErrRecordNotFound {
			return
		}
	} else if existProd.Status.Valid {
		err = ecode.AppendMessage(constant.ErrDuplicateInput, "存在同名商品")
		return
	}
	product := &model.Product{
		Name:        req.Name,
		SgId:        req.Token.SgId,
		PStoreId:    req.PStoreId,
		Nature:      uint8(req.Nature),
		TypeId:      req.ProdTypeId,
		Phonetic:    req.Phonetic,
		TitlePics:   req.TitlePics,
		DetailPics:  req.DetailPics,
		Description: req.DetailDescription,
		Unit:        req.Unit,
		Operator:    req.Token.EmpId,
		Status:      sql.NullBool{Valid: true, Bool: true},
	}
	if skus, product.GroupList, product.GroupDetail, err = s.verifySkuList(
		req.SkuList, req.Token.SgId, req.Token.EmpId); err != nil {
		return
	}
	// 数据落地
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if err = s.dao.Create(ctx, product); err != nil {
			return
		}
		for _, sku := range skus {
			sku.ProdId = product.ID
			if err = s.dao.Create(ctx, sku); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	resp, err = s.getProdInfo(ctx, product.ID, req.Token.SgId, req.PStoreId, false)
	return
}

func (s *Service) EditProd(ctx context.Context, req *prod.EditProdRequest) (resp *prod.ProdView, err error) {
	if err = s.dao.CheckPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var (
			existProd     *model.Product
			rawSkus, skus []*model.Sku
			nsqMsg        []byte
			delSkuIds     []uint64
		)
		// 锁定正确状态的记录
		product := &model.Product{}
		if err = s.dao.LockObjectByID(ctx, product, req.Id, req.Token.SgId, req.PStoreId); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
			}
			return
		}
		// 加载规格（含删除规格项）
		if rawSkus, err = s.dao.GetAllProdSkusByProdIds(ctx, []uint64{req.Id}); err != nil {
			return
		}
		// 同名检查
		if product.Name != req.Name {
			if existProd, err = s.dao.GetProdInfoByName(ctx, req.Name, req.Token.SgId, req.PStoreId); err != nil {
				if errors.Cause(err) != gorm.ErrRecordNotFound {
					return
				}
			} else if existProd.Status.Valid {
				err = ecode.AppendMessage(constant.ErrDuplicateInput, "存在同名商品")
				return
			}
		}
		// 规格比对
		rawSkuMap := make(map[string]*model.Sku)
		for _, sku := range rawSkus {
			rawSkuMap[strings.Join(sku.Items, "/")] = sku
		}
		if skus, product.GroupList, product.GroupDetail, err = s.verifySkuList(
			req.SkuList, req.Token.SgId, req.Token.EmpId); err != nil {
			return
		}
		for _, sku := range skus {
			skuName := strings.Join(sku.Items, "/")
			if rawSku, ok := rawSkuMap[skuName]; ok {
				// 历史规格更新
				sku.ID = rawSku.ID
				delete(rawSkuMap, skuName)
			}
			sku.ProdId = req.Id
			// 更新&新建规格项
			if err = s.dao.Save(ctx, sku); err != nil {
				return
			}
		}
		// 删除不存在的规格项
		for _, sku := range rawSkuMap {
			if sku.Status {
				delSkuIds = append(delSkuIds, sku.ID)
			}
		}
		if len(delSkuIds) != 0 {
			if err = s.dao.DeleteSkuByIDs(ctx, delSkuIds, req.Id, req.Token.EmpId); err != nil {
				return
			}
		}
		product.Name = req.Name
		product.Phonetic = req.Phonetic
		product.TypeId = req.ProdTypeId
		product.TitlePics = req.TitlePics
		product.DetailPics = req.DetailPics
		product.Description = req.DetailDescription
		product.Unit = req.Unit
		product.Operator = req.Token.EmpId
		if err = s.dao.Save(ctx, product); err != nil {
			return
		}
		// 发送商品更新消息
		if nsqMsg, err = json.Marshal(&prod.ProdUpdateMessage{
			Ids:       []uint64{req.Id},
			SgId:      req.Token.SgId,
			Timestamp: time.Now().Local().Unix(),
		}); err != nil {
			err = ecode.WithStack(err)
			return
		}
		if err = s.dao.PublishRepeatedNSQMessage(
			ctx, constant.TopicProdUpdate, strconv.Itoa(int(req.Id)), nsqMsg); err != nil {
			return err
		}
		return
	}); err != nil {
		return
	}
	resp, err = s.getProdInfo(ctx, req.Id, req.Token.SgId, req.PStoreId, false)
	return
}

func (s *Service) BatchDeleteProd(ctx context.Context, req *prod.CommonObjectListRequest) (
	resp *prod.DefaultResp, err error) {
	if err = s.dao.CheckPermission(ctx, req.Token, req.PStoreId); err != nil {
		return
	}
	var prods []*model.Product
	if prods, err = s.dao.GetProdByIDs(ctx, req.Ids, req.Token.SgId, req.PStoreId); err != nil {
		return
	}
	if len(prods) != len(req.Ids) {
		err = ecode.AppendMessage(constant.ErrNeedRefresh, "所选商品包含已删除商品，请刷新列表重试")
		return
	}
	prodNameMap := make(map[uint64]string)
	for _, p := range prods {
		prodNameMap[p.ID] = p.Name
	}

	// 1. 检查各系统是否允许删除
	if err = s.dao.PreDeleteCheck(ctx, req.Token.SgId, req.Ids, prodNameMap); err != nil {
		return
	}
	if err = s.dao.ShelfProdsOnShelfCheck(ctx, req.Token.SgId, req.Ids, prodNameMap); err != nil {
		return
	}
	if err = s.dao.CheckProdsExistsInMenu(ctx, req.Ids); err != nil {
		if errors.Cause(err) != gorm.ErrRecordNotFound {
			return
		}
	} else {
		err = ecode.AppendMessage(constant.ErrNeedRefresh, "商品在供货组中，不能删除")
		return
	}
	// 3. 执行删除
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		var (
			skus   []*model.Sku
			nsqMsg []byte
		)
		// 锁定正确状态的记录
		if _, err = s.dao.LockProdByIDs(ctx, req.Ids, req.Token.SgId, req.PStoreId); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
			}
			return
		}
		// 加载有效规格
		if skus, err = s.dao.GetProdSkusByProdIds(ctx, req.Ids, req.Token.SgId); err != nil {
			return
		}
		for _, sku := range skus {
			sku.Status = false
			sku.Operator = req.Token.EmpId
			if err = s.dao.Save(ctx, sku); err != nil {
				return
			}
		}
		if err = s.dao.DeleteProdByIDs(ctx, req.Ids, req.Token.EmpId); err != nil {
			return
		}
		// 发送商品删除消息
		if nsqMsg, err = json.Marshal(&prod.ProdDeleteMessage{
			Ids:       req.Ids,
			SgId:      req.Token.SgId,
			Timestamp: time.Now().Local().Unix(),
		}); err != nil {
			err = ecode.WithStack(err)
			return
		}
		if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicProdDelete, nsqMsg); err != nil {
			return err
		}
		return
	}); err != nil {
		return
	}
	resp = &prod.DefaultResp{Status: true}
	return
}
